﻿using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;

namespace Gmantis.Controls
{
    public static class PropertyPathHelper
    {
        public static Expression Convert(Expression exp, Type type)
        {
            Expression expression = exp;
            if (exp.Type != type)
            {
                try
                {
                    if (type.IsNullableType() && (type.GetNonNullableType() == exp.Type))
                    {
                        return Expression.Convert(expression, type);
                    }
                    expression = Expression.Call(typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type), typeof(IFormatProvider) }), new Expression[] { exp, Expression.Constant(type, typeof(Type)), Expression.Constant(null, typeof(IFormatProvider)) });
                    expression = Expression.Convert(expression, type);
                }
                catch
                {
                }
            }
            return expression;
        }

        public static Func<T, X> CreateSelector<T, X>(string path)
        {
            ParameterExpression expression;
            Expression nullCheck = Expression.Constant(true, typeof(bool));
            return Expression.Lambda<Func<T, X>>(MakePropertyAccess(typeof(T), expression = Expression.Parameter(typeof(T), "i"), path, ref nullCheck), new ParameterExpression[] { expression }).Compile();
        }

        public static Func<object, X> CreateSelector<X>(Type type, string path)
        {
            ParameterExpression expression;
            Expression nullCheck = Expression.Constant(true, typeof(bool));
            return Expression.Lambda<Func<object, X>>(MakePropertyAccess(type, Expression.Convert(expression = Expression.Parameter(typeof(object), "i"), type), path, ref nullCheck), new ParameterExpression[] { expression }).Compile();
        }

        public static object GetPropertyPathValue(object item, string propertyPath)
        {
            return item.GetPropertyValue<object>(propertyPath);
        }

        internal static bool IsIndexedProperty(string propertyPath)
        {
            return propertyPath.Contains("[");
        }

        internal static Expression MakeIndexPropertyAccess(Type type, Expression currentPropertyAccess, string propertyPath, Type indexType, ref Expression nullCheck)
        {
            string str;
            string str2;
            Expression expression = null;
            if (propertyPath.StartsWith("["))
            {
                str = "Item";
                str2 = propertyPath.Substring(1, propertyPath.IndexOf(']') - 1);
                foreach (PropertyInfo info in type.GetProperties())
                {
                    ParameterInfo[] indexParameters = info.GetIndexParameters();
                    if ((info.Name.Equals(str) && (indexParameters.Length == 1)) && (indexParameters[0].ParameterType == indexType))
                    {
                        try
                        {
                            object obj2 = System.Convert.ChangeType(str2, indexType, null);
                            expression = Expression.Property(currentPropertyAccess, info, new Expression[] { Expression.Constant(obj2) });
                        }
                        catch
                        {
                        }
                        return expression;
                    }
                }
                return expression;
            }
            str = propertyPath.Substring(0, propertyPath.IndexOf('['));
            str2 = propertyPath.Substring(propertyPath.IndexOf('['), (propertyPath.IndexOf(']') - propertyPath.IndexOf('[')) + 1);
            expression = MakePropertyAccess(type, currentPropertyAccess, str, ref nullCheck);
            return MakeIndexPropertyAccess(type.GetProperty(str).PropertyType, expression, str2, indexType, ref nullCheck);
        }

        public static Expression MakePropertyAccess(Type type, Expression param, string propertyPath, ref Expression nullCheck)
        {
            string[] strArray = propertyPath.Split(new char[] { '.' });
            Expression currentPropertyAccess = param;
            for (int i = 0; i < strArray.Length; i++)
            {
                if (IsIndexedProperty(strArray[i]))
                {
                    Expression expression2 = MakeIndexPropertyAccess(type, currentPropertyAccess, strArray[i], typeof(int), ref nullCheck);
                    if (expression2 == null)
                    {
                        expression2 = MakeIndexPropertyAccess(type, currentPropertyAccess, strArray[i], typeof(string), ref nullCheck);
                    }
                    currentPropertyAccess = expression2;
                }
                else if (type.GetInterfaces().Contains<Type>(typeof(ICustomTypeProvider)))
                {
                    currentPropertyAccess = Expression.Call(typeof(PropertyPathHelper).GetMethod("GetPropertyPathValue", new Type[] { typeof(object), typeof(string) }), currentPropertyAccess, Expression.Constant(strArray[i], typeof(string)));
                }
                else
                {
                    currentPropertyAccess = Expression.Property(currentPropertyAccess, strArray[i]);
                }
                if (!currentPropertyAccess.Type.IsValueType && (i < (strArray.Length - 1)))
                {
                    nullCheck = Expression.AndAlso(nullCheck, Expression.NotEqual(currentPropertyAccess, Expression.Constant(null, typeof(object))));
                }
            }
            return currentPropertyAccess;
        }
    }
}
